// Copyright 2013 SICK AG. All rights reserved.
package de.sick.guicheck.fx;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.stage.Modality;
import javafx.stage.Stage;
import com.sun.javafx.robot.FXRobot;
import com.sun.javafx.stage.StageHelper;
import de.sick.guicheck.GcAssertException;
/**
* A JavaFX robot which automatically waits for the windowing thread to become idle. Input device methods like mouse or
* keyboard actions are delegated to the {@link FXRobot} given in the constructor.
* <p>
* Every method which triggers an input action, like mouse or keyboard, checks if the platform is still alive, calls the
* corresponding method of the robot and waits for the UI becoming idle.
*
* @see FXRobot
* @author linggol (created)
*/
public class GcRobotFX
{
private final FXRobot m_robot;
private final GcStageFX m_stage;
GcRobotFX(GcStageFX stage, FXRobot robot)
{
m_stage = stage;
m_robot = robot;
}
/**
* @see FXRobot#keyPress(KeyCode)
*/
public GcRobotFX keyPress(KeyCode code)
{
if (GcUtilsFX.isPlatformAlive())
{
checkForModalChildStages();
m_robot.keyPress(code);
GcUtilsFX.waitForIdle();
}
return this;
}
/**
* @see FXRobot#keyRelease(KeyCode)
*/
public GcRobotFX keyRelease(KeyCode code)
{
if (GcUtilsFX.isPlatformAlive())
{
checkForModalChildStages();
m_robot.keyRelease(code);
GcUtilsFX.waitForIdle();
}
return this;
}
/**
* @see FXRobot#keyPress(KeyCode)
* @see FXRobot#keyRelease(KeyCode)
*/
public GcRobotFX keyType(KeyCode... codes)
{
if (GcUtilsFX.isPlatformAlive())
{
checkForModalChildStages();
for (KeyCode c : codes)
{
m_robot.keyPress(c);
GcUtilsFX.waitForIdle();
m_robot.keyRelease(c);
GcUtilsFX.waitForIdle();
}
}
return this;
}
/**
* @see FXRobot#keyType(KeyCode, String)
*/
public GcRobotFX keyType(KeyCode code, String keyChar)
{
if (GcUtilsFX.isPlatformAlive())
{
checkForModalChildStages();
m_robot.keyType(code, keyChar);
GcUtilsFX.waitForIdle();
}
return this;
}
/**
* Type the given string on the keyboard. The component having the focus will get the resulting {@link KeyEvent}
*/
public GcRobotFX keyType(String s)
{
if (GcUtilsFX.isPlatformAlive())
{
checkForModalChildStages();
for (char c : s.toCharArray())
{
final KeyCode l_code = KeyCode.getKeyCode(String.valueOf(c));
m_robot.keyType(l_code == null ? KeyCode.UNDEFINED : l_code, Character.toString(c));
GcUtilsFX.waitForIdle();
}
}
return this;
}
/**
* @see FXRobot#mouseWheel(int)
*/
public GcRobotFX mouseWheel(int wheelAmt)
{
if (GcUtilsFX.isPlatformAlive())
{
checkForModalChildStages();
m_robot.mouseWheel(wheelAmt);
GcUtilsFX.waitForIdle();
}
return this;
}
/**
* Moves the mouse to the center point of the node given via CSS ID.
*
* @see FXRobot#mouseMove(int, int)
*/
public GcRobotFX mouseMoveToCenter(String selector)
{
return mouseMoveToCenter(m_stage.node(selector));
}
/**
* Moves the mouse to the center point of the given component.
*
* @see FXRobot#mouseMove(int, int)
*/
public GcRobotFX mouseMoveToCenter(GcComponentFX<?> component)
{
Bounds l_bounds = component.getNode().getBoundsInLocal();
return mouseMove(component, (int)(l_bounds.getWidth() / 2), (int)(l_bounds.getHeight() / 2));
}
/**
* Moves the mouse to the given point in the scene.
*
* @see FXRobot#mouseMove(int, int)
*/
public GcRobotFX mouseMove(int x, int y)
{
if (GcUtilsFX.isPlatformAlive())
{
checkForModalChildStages();
m_robot.mouseMove(x, y);
GcUtilsFX.waitForIdle();
}
return this;
}
/**
* Moves the mouse to the given point relative to the component given by selector.
*
* @see FXRobot#mouseMove(int, int)
*/
public GcRobotFX mouseMove(String selector, int x, int y)
{
return mouseMove(m_stage.node(selector), x, y);
}
/**
* Moves the mouse to the given point relative to the component.
*
* @see FXRobot#mouseMove(int, int)
*/
public GcRobotFX mouseMove(GcComponentFX<?> component, int x, int y)
{
Point2D l_point = component.getNode().localToScene(x, y);
return mouseMove((int)l_point.getX(), (int)l_point.getY());
}
/**
* Presses the primary mouse button.
*
* @see FXRobot#mousePress(MouseButton, int)
*/
public GcRobotFX mousePress()
{
checkForModalChildStages();
internalMousePress(MouseButton.PRIMARY, 1);
return this;
}
/**
* Presses the secondary mouse button.
*
* @see FXRobot#mousePress(MouseButton, int)
*/
public GcRobotFX mousePressSecondary()
{
checkForModalChildStages();
internalMousePress(MouseButton.SECONDARY, 1);
return this;
}
/**
* Releases the primary mouse button.
*
* @see FXRobot#mouseRelease(MouseButton, int)
*/
public GcRobotFX mouseRelease()
{
checkForModalChildStages();
internalMouseRelease(MouseButton.PRIMARY, 1);
return this;
}
/**
* Releases the secondary mouse button.
*
* @see FXRobot#mouseRelease(MouseButton, int)
*/
public GcRobotFX mouseReleaseSecondary()
{
checkForModalChildStages();
internalMouseRelease(MouseButton.SECONDARY, 1);
return this;
}
/**
* Clicks the primary mouse button. Automatically adds mousePress and mouseRelease calls.
*
* @see FXRobot#mouseClick(MouseButton, int)
*/
public GcRobotFX mouseClick()
{
checkForModalChildStages();
internalMouseClick(MouseButton.PRIMARY, 1);
return this;
}
/**
* Clicks the secondary mouse button. Automatically adds mousePress and mouseRelease calls.
*
* @see FXRobot#mouseClick(MouseButton, int)
*/
public GcRobotFX mouseClickSecondary()
{
checkForModalChildStages();
internalMouseClick(MouseButton.SECONDARY, 1);
return this;
}
/**
* Double-clicks the primary mouse button. Automatically adds mousePress and mouseRelease calls.
*
* @see FXRobot#mouseClick(MouseButton, int)
*/
public GcRobotFX mouseDblClick()
{
checkForModalChildStages();
internalMouseClick(MouseButton.PRIMARY, 1);
internalMouseClick(MouseButton.PRIMARY, 2);
return this;
}
/**
* Double-clicks the secondary mouse button. Automatically adds mousePress and mouseRelease calls.
*
* @see FXRobot#mouseClick(MouseButton, int)
*/
public GcRobotFX mouseDblClickSecondary()
{
checkForModalChildStages();
internalMouseClick(MouseButton.SECONDARY, 1);
internalMouseClick(MouseButton.SECONDARY, 2);
return this;
}
/**
* Drags with the primary mouse button pressed.
*
* @see FXRobot#mouseDrag(MouseButton)
*/
public GcRobotFX mouseDrag()
{
internalMouseDrag(MouseButton.PRIMARY);
return this;
}
/**
* Drags with the secondary mouse button pressed.
*
* @see FXRobot#mouseDrag(MouseButton)
*/
public GcRobotFX mouseDragSecondary()
{
internalMouseDrag(MouseButton.SECONDARY);
return this;
}
private void internalMouseClick(MouseButton button, int clickCount)
{
internalMousePress(button, clickCount);
internalMouseRelease(button, clickCount);
if (GcUtilsFX.isPlatformAlive())
{
m_robot.mouseClick(button, clickCount);
GcUtilsFX.waitForIdle();
}
}
private void internalMouseDrag(MouseButton button)
{
if (GcUtilsFX.isPlatformAlive())
{
m_robot.mouseDrag(button);
GcUtilsFX.waitForIdle();
}
}
private void internalMousePress(MouseButton button, int clickCount)
{
if (GcUtilsFX.isPlatformAlive())
{
checkForModalChildStages();
m_robot.mousePress(button, clickCount);
GcUtilsFX.waitForIdle();
}
}
private void internalMouseRelease(MouseButton button, int clickCount)
{
if (GcUtilsFX.isPlatformAlive())
{
checkForModalChildStages();
m_robot.mouseRelease(button, clickCount);
GcUtilsFX.waitForIdle();
}
}
/**
* Set the focus to the component given via CSS selector. Setting the focus is done via an explicit call to
* {@link Node#requestFocus()} and not using any method of the {@link FXRobot}.
*/
public GcRobotFX focus(String selector)
{
return focus(m_stage.node(selector));
}
/**
* Set the focus to the given component. Setting the focus is done via an explicit call to
* {@link Node#requestFocus()} and not using any method of the {@link FXRobot}.
*/
public GcRobotFX focus(final GcComponentFX<?> component)
{
checkForModalChildStages();
GcUtilsFX.runLaterAndWait(new Runnable()
{
@Override
public void run()
{
component.getNode().requestFocus();
}
});
GcUtilsFX.waitForIdle();
return this;
}
private void checkForModalChildStages()
{
for (Stage s : StageHelper.getStages())
{
if (s.getOwner() == m_stage.getFXComponent() && s.isShowing() && s.getModality() != Modality.NONE)
{
throw new GcAssertException("The stage <" + ((Stage)m_stage.getFXComponent()).getTitle() + "> is blocked by the modal child window <" + s.getTitle() + ">");
}
}
}
}